package com.ckerchina.admin.ckadmin.utils;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Stack;

public class FormulaCaculateUtil {

	public static void main(String[] args) {
		BigDecimal num = FormulaCaculateUtil.caculate("a*b+c*d+e*f",
				"{\"a\":1.0,\"b\":1.0,\"c\":1.0,\"d\":1.0,\"e\":1.0,\"f\":1.0,\"m\":1.0,\"n\":1.0}");
		System.out.println(num);
	}

	// 中缀表达式转换后缀表达式（逆波兰式）
	@SuppressWarnings("unchecked")
	public static BigDecimal caculate(String expression, String paramJson) {
		Map<String, Double> param = JsonMapper.fromJson(paramJson, Map.class);
		if (param == null) {
			return BigDecimal.ZERO;
		}
		try {
			return new BigDecimal(expression).setScale(1, BigDecimal.ROUND_HALF_UP);
		} catch (NumberFormatException e) {
		}
		// Stack<Double> s2=new Stack<Double>();//存放数字栈
		Stack<Object> s3 = new Stack<Object>();// 存放结果栈
		Stack<Character> s4 = new Stack<Character>();// 存放操作符栈
		int len = expression.length();//
		char c1;
		String var;
		int m, n = -1;
		for (int i = 0; i < len; i++) {
			c1 = expression.charAt(i);
			if (isOprator(c1) || (i == len - 1)) { // 如果是运算符，将前面的数数字入s3栈，操作符入s4栈
				if (i == len - 1 && (!isOprator(c1))) // 当最后一位且不是操作符时，将前面的数压栈
					m = i + 1;
				else
					m = i;
				// 操作数入栈,向前遍历到下一个运算符，将中间的字符串转化为double
				for (int j = i - 1; j >= 0; j--) {
					if (isOprator(expression.charAt(j))) {
						n = j;
						break;
					}
					n = j - 1;
				}
				if (m != n + 1) { // 只有当这两个值不等时中间才会有操作数
					var = expression.substring(n + 1, m);
					s3.push(var);
				}
				// 运算符入栈
				if (i == 0 && (c1 != '(')) { // 当表达式第一个字符就为运算符且不是左括号时，返回表达式错误
					throw new RuntimeException("表达式错误！");
				} else if (isOprator(c1)) { // 且是操作符时
					while (true) { // 如果栈为空或者栈顶元素为（或者c1为（时，则直接将运算符压入栈内
						if (s4.isEmpty() || s4.peek() == '(' || c1 == '(') {
							s4.push(c1);
							break;
						} else if (c1 == ')') { // 当c1为）时，依次弹出s4中的运算符并压入s3，直到（，舍弃这一对括号
							while (s4.peek() != '(') {
								s3.push(s4.pop());
								if (s4.isEmpty()) { // 弹出所有不为左括号之后堆栈为空，则表达式不合法
									throw new RuntimeException("缺少左括号");
								}
							}
							s4.pop();// 弹出
							break;
						} else { // 判断优先级，优先级高入栈，优先级低将栈顶运算符压入s3
							if (priorityCompare(c1, s4.peek()) == 1) {
								s4.push(c1);
								break;
							} else {
								s3.push(s4.pop());
							}
						}
					}
				}
			} else
				continue;
		}
		while (!s4.isEmpty()) { // 表达式结束后，依次将s4剩下的运算符压入s3
			if ((char) s4.peek() == '(') {
				throw new RuntimeException("缺少右括号");
			}
			s3.push(s4.pop());
		}
		return count_result(s3, param).setScale(1, BigDecimal.ROUND_HALF_UP);
	}

	private static int priorityCompare(char c1, char c2) {
		switch (c1) {
		case '+':
		case '-':
			return (c2 == '*' || c2 == '/' ? -1 : 0);
		case '*':
		case '/':
			return (c2 == '+' || c2 == '-' ? 1 : 0);
		}
		return 1;
	}

	// 判断字符是否为运算符，是为真，不是为假
	private static boolean isOprator(Object c) {
		try {
			char c1 = (Character) c;
			if (c1 == '+' || c1 == '-' || c1 == '*' || c1 == '/' || c1 == '(' || c1 == ')')
				return true;
		} catch (Exception e) {
			return false;
		}
		return false;
	}

	private static BigDecimal count_result(Stack<Object> ob, Map<String, Double> param) {
		Stack<Object> s1 = new Stack<Object>();// 后缀表达式栈
		Stack<String> s2 = new Stack<String>();// 操作数栈
		// char c1;
		// Stack<Character> s3=new Stack<Character>();//操作符栈
		while (!ob.isEmpty()) { // 将传入的栈逆序压入
			s1.push(ob.pop());
		}
		while (!s1.isEmpty()) {
			if (!isOprator(s1.peek())) { // 遇到非操作符，压入s2栈
				s2.push((String) s1.pop());
			} else {
				s2.push(count(s2.pop(), s2.pop(), (Character) s1.pop(), param));
			}
		}
		return getValue(s2.peek(), param);
	}

	private static String count(String s1, String s2, char s3, Map<String, Double> param) {
		BigDecimal result = new BigDecimal(0.0);
		switch (s3) {
		case '+':
			result = getValue(s1, param).add(getValue(s2, param));
			break;
		case '-':
			result = getValue(s2, param).subtract(getValue(s1, param));
			break;
		case '*':
			result = getValue(s1, param).multiply(getValue(s2, param));
			break;
		case '/':
			result = getValue(s2, param).divide(getValue(s1, param));
			break;
		}
		return result + "";
	}

	private static BigDecimal getValue(String s2, Map<String, Double> param) {
		try {
			return new BigDecimal(s2);
		} catch (NumberFormatException e) {
			return new BigDecimal(String.valueOf(param.get(s2)));
		}
	}
}
